package com.company;

import com.company.common.Constants;
import com.company.service.ICacheService;
import com.company.utils.JsonUtil;
import com.company.utils.RequestUtil;
import com.company.utils.ValidateUtil;
import com.google.common.base.Strings;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * Created by tu on 2017/4/28.
 */
@Aspect
@Component
public class ApiAspect {

  private static final Logger logger = LoggerFactory.getLogger(ApiAspect.class);

  @Autowired
  ICacheService cacheService;

  @Around("execution(public * com.company.*.*Controller.*(..))")
  public Object process(ProceedingJoinPoint joinPoint) throws Throwable {
    //input
    String methodInfo = this.getMethodInfo(joinPoint);
    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
        .getRequestAttributes()).getRequest();

    if (HttpMethod.POST.name().equalsIgnoreCase(request.getMethod())) {
      Object[] paramsArray = joinPoint.getArgs();
      String params = argsArrayToString(paramsArray);
      logger.info("{}, input params:{}", logPrefix(methodInfo, request), params);
      String userAgent = RequestUtil.getUserAgent(request);
      //45s一次提交
      final String key = Constants.REDIS_KEY_USER_AGENT_PREFIX + userAgent;
      if (cacheService.exists(key)) {
        logger.info("RateLimiter,30s,userAgent:{}", userAgent);
        cacheService.setex(key, 30, new Date().toString());
        throw new CompanyException("妖怪,又来刷副本");
      }
      cacheService.setex(key, 30, new Date().toString());

      //1小时内，重复100次
      String countKey = Constants.REDIS_KEY_USER_AGENT_COUNT_PREFIX + userAgent;
      String count = cacheService.get(countKey);
      if (Strings.isNullOrEmpty(count)) {
        cacheService.setex(countKey, 3600, "1");
      } else {
        int countInt = Integer.valueOf(count);
        if (countInt >= 100) {
          logger.info("RateLimiter,3600s,userAgent:{},count:{}", userAgent, count);
          cacheService.setex(countKey, 3600, String.valueOf(countInt + 1));
          throw new CompanyException("妖怪,又来刷副本");
        }
        cacheService.setex(countKey, 3600, String.valueOf(countInt + 1));
      }
    } else {
      logger.info("{}, input params:{}", logPrefix(methodInfo, request),
          JsonUtil.toJson(request.getParameterMap()));
    }

    //validate
    this.formValidate(joinPoint.getArgs(), getClass().getPackage().getName());
    Object respData = joinPoint.proceed();

    //output
    long startTime = System.currentTimeMillis();
    long endTime = System.currentTimeMillis();

    String returnInfo;
    if (null != respData && respData.getClass().isAssignableFrom(ResponseEntity.class)) {
      ResponseEntity<?> respDataResponseEntity = (ResponseEntity<?>) respData;
      returnInfo = JsonUtil.toJson(respDataResponseEntity.getBody());
    } else {
      returnInfo = JsonUtil.toJson(respData);
    }

    logger
        .info("{}, output params:{}, Duration[{}]ms", logPrefix(methodInfo, request),
            returnInfo,
            endTime - startTime);
    return respData;
  }

  private void formValidate(Object[] args, String packageScan) throws ValidationException {
    Assert
        .hasLength(packageScan, "You must specify a package Which arguments in will be validated");
    for (int i = 0, len = args.length; i < len; i++) {
      Object arg = args[i];
      if (null != arg && arg.getClass().getName().startsWith(packageScan)) {
        String result = ValidateUtil.validate(args[i]);
        if (!ObjectUtils.isEmpty(result)) {
          throw new ValidationException(result);
        }
      }
    }
  }

  private static String logPrefix(String methodInfo, HttpServletRequest request) {
    return "[" + request.getMethod() + "]" + methodInfo + "()-->" + request.getRequestURI()
        + "被调用;客户端UserAgent=" + RequestUtil.getUserAgent(request);
  }

  private String getMethodInfo(ProceedingJoinPoint joinPoint) {
    return this.getClassName(joinPoint) + "." + this.getMethodName(joinPoint);
  }

  private String getClassName(ProceedingJoinPoint joinPoint) {
    return joinPoint.getTarget().getClass().getSimpleName();
  }

  private String getMethodName(ProceedingJoinPoint joinPoint) {
    return joinPoint.getSignature().getName();

  }

  private String argsArrayToString(Object[] paramsArray) {
    String params = "";
    if (paramsArray != null && paramsArray.length > 0) {
      for (Object arg : paramsArray) {
        if (null != arg && arg.getClass().getName()
            .startsWith(this.getClass().getPackage().getName())) {
          String json = JsonUtil.toJson(arg);
          params += json.toString() + " ";
        }
      }
    }
    return params.trim();
  }
}
